package com.ruoyi.activiti.service.impl;

import com.ruoyi.activiti.condition.ProcessDefinitionCondition;
import com.ruoyi.activiti.condition.TodoCondition;
import com.ruoyi.activiti.domain.IProcessDefinition;
import com.ruoyi.activiti.domain.IProcessInstance;
import com.ruoyi.activiti.domain.ITask;
import com.ruoyi.activiti.domain.ProcessDiagramCanvas;
import com.ruoyi.activiti.mapper.ActivitiMapper;
import com.ruoyi.activiti.service.ActivitiService;
import com.ruoyi.activiti.service.AssigneeService;
import com.ruoyi.activiti.service.Candidate;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.mapper.SysRoleMapper;
import com.ruoyi.system.service.impl.SysRoleServiceImpl;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricTaskInstanceQuery;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.activiti.spring.ProcessEngineFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.awt.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;

import static com.ruoyi.common.core.domain.AjaxResult.error;
import static com.ruoyi.common.core.domain.AjaxResult.success;

/**
 * @author danghf
 * @date 2024/04/26 18:56:26
 */
@Service
public class ActivitiServiceImpl implements ActivitiService {
    /**
     * 流程引擎
     */
    private final ProcessEngineFactoryBean processEngine;

    /**
     * 仓库管理
     */
    private final RepositoryService repositoryService;

    /**
     * 运行管理
     */
    private final RuntimeService runtimeService;

    /**
     * 任务管理
     */
    private final TaskService taskService;

    /**
     * 历史管理
     */
    private final HistoryService historyService;

    private final ActivitiMapper mapper;

    private final AssigneeService assigneeService;

    public ActivitiServiceImpl(ProcessEngineFactoryBean processEngine, RepositoryService repositoryService, RuntimeService runtimeService, TaskService taskService, HistoryService historyService
    , ActivitiMapper mapper, AssigneeService assigneeService) {
        this.processEngine = processEngine;
        this.repositoryService = repositoryService;
        this.runtimeService = runtimeService;
        this.taskService = taskService;
        this.historyService = historyService;
        this.mapper = mapper;
        this.assigneeService = assigneeService;
    }

    /**
     * 部署流程
     */
    @Override
    public AjaxResult deployment(@RequestParam("file") MultipartFile file) {
        FileInputStream fileInputStream = null;
        try {
            String fileOriginalFilename = file.getOriginalFilename();
            if (fileOriginalFilename == null || !fileOriginalFilename.contains(".")) {
                System.out.printf("流程部署失败: %s\n", "文件名不合法");
                return error("流程部署失败: 文件名不合法");
            }
            // 获取文件的后缀
            String fileExtension = fileOriginalFilename.substring(fileOriginalFilename.lastIndexOf(".") + 1);
            if (!"bpmn".equals(fileExtension) && !"xml".equals(fileExtension)) {
                System.out.printf("流程部署失败: %s\n", "文件类型不合法");
                return error("流程部署失败: 文件名不合法");
            }
            fileInputStream = (FileInputStream) file.getInputStream();
            // resource 名称必须为 bpmn / bpmn20.xml结尾
            Deployment deployment = repositoryService.createDeployment()
                    .name(fileOriginalFilename)
                    .addInputStream(fileOriginalFilename, fileInputStream)
                    .disableSchemaValidation()
                    .deploy();
            return success("流程部署成功 \n流程部署id: " + deployment.getId() + "\n 流程部署名称: " + deployment.getName());
        } catch (IOException e) {
            return error("流程部署失败: " + e.getMessage());
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 获取文件资源
     *
     * @param deploymentKey     部署key
     * @param deploymentVersion 部署版本
     * @param resourceType      资源类型
     */
    @Override
    public InputStream getFileResource(String deploymentKey, String deploymentVersion, String resourceType) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(deploymentKey)
                .processDefinitionVersion(Integer.parseInt(deploymentVersion))
                .singleResult();
        if (processDefinition == null) {
            return null;
        }
        if ("xml".equals(resourceType)) {
            return repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
        }

        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        DefaultProcessDiagramGenerator generator = new DefaultProcessDiagramGenerator();
        return generator.generateDiagram(bpmnModel, "宋体", "宋体", "宋体");
    }

    /**
     * 获取流程定义
     */
    @Override
    public List<IProcessDefinition> getProcessDefinitionKeys(ProcessDefinitionCondition condition) {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (condition.getProcessDefinitionKey() != null) {
            processDefinitionQuery.processDefinitionKey(condition.getProcessDefinitionKey());
        }
        if (condition.getProcessDefinitionName() != null) {
            processDefinitionQuery.processDefinitionNameLike("%" + condition.getProcessDefinitionName() + "%");
        }
        if (condition.getProcessDefinitionVersion() != null) {
            processDefinitionQuery.processDefinitionVersion(Integer.parseInt(condition.getProcessDefinitionVersion()));
        }
        List<ProcessDefinition> processDefinitions = processDefinitionQuery.list();
        // ProcessDefinition序列化IdentityLink报空异常
        List<IProcessDefinition> processList = new ArrayList<>();
        for (ProcessDefinition processDefinition : processDefinitions) {
            processList.add(IProcessDefinition.build(processDefinition));
        }
        return processList;
    }

    @Override
    public AjaxResult cancelDeployment(String deploymentId, boolean cascade) {
        try {
            repositoryService.deleteDeployment(deploymentId, cascade);
            return success("流程部署撤销成功");
        } catch (Exception e) {
            return error("流程部署撤销失败: " + e.getMessage());
        }
    }

    @Override
    public List<?> getMeToDone(TodoCondition condition) {
        Long userId = SecurityUtils.getUserId();
        List<Long> roles = assigneeService.selectRoleListByUserId(userId);
        return handlerCandidate(mapper.getMeToDone(userId, roles, condition));
    }

    @Override
    public AjaxResult completeTask(String taskId, Map<String,Object> variables) {
        // 判断当前用户是否可以执行当前任务
        Long userId = SecurityUtils.getUserId();
        List<Long> roleIds = assigneeService.selectRoleListByUserId(userId);
        if (hasExecutionAuthority(userId, roleIds, taskId)){
            taskService.claim(taskId, userId.toString());
            taskService.complete(taskId, variables);
            return success();
        }
        return error("任务执行失败: 无权限执行当前任务");
    }

    /**
     * 判断当前用户是否有执行权限
     * @param userId 用户id
     * @param roleIds 角色id
     * @param taskId 任务id
     */
    private boolean hasExecutionAuthority(Long userId, List<Long> roleIds, String taskId) {
        return mapper.hasExecutionAuthority(userId, roleIds, taskId) > 0;
    }

    @Override
    public List<?> getAllToDone(TodoCondition condition) {
        return handlerCandidate(mapper.getAllToDone(condition));
    }

    @Override
    public List<?> getCompletedTaskWithMe(TodoCondition condition) {
        return mapper.getCompletedTaskWithMe(SecurityUtils.getUserId(), condition);
    }

    @Override
    public AjaxResult forceTerminateProcessInstance(String processInstanceId, String message) {

        String username = SecurityUtils.getUsername();
        Long userId = SecurityUtils.getUserId();
        String deleteReason = "[username="+username + "; userId="
                + userId + "; "
                + "强制终止 forceTerminateProcessInstance=" + processInstanceId+"]";
        deleteReason = deleteReason + message;
        runtimeService.deleteProcessInstance(processInstanceId, deleteReason);
        return success("流程实例强制终止成功");
    }

    @Override
    public List<?> getProcessInstances(TodoCondition condition) {
        return handlerCandidate(mapper.getProcessInstances(condition));
    }

    @Override
    public List<?> getProcessInstanceByUserId(TodoCondition condition) {
        List<IProcessInstance> processInstances = mapper.getProcessInstanceByUserId(SecurityUtils.getUserId(), condition);
        return handlerCandidate(processInstances);
    }

    @Override
    public AjaxResult startByProcessDefinitionKey(String processDefinitionKey, Map<String, Object> variables) {
        Authentication.setAuthenticatedUserId(SecurityUtils.getUserId().toString());
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
        return processInstance == null ? error("启动失败") : success("流程启动成功" );
    }

    @Override
    public AjaxResult rejected(String taskId) {
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult();
        String processInstanceId = task.getProcessInstanceId();
        // 获取该实例  所有任务节点实例(不包含start) 降序
        // act_hi_taskinst
        List<HistoricTaskInstance> hisTaskList = historyService
                .createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByTaskCreateTime()
                .desc()
                .list();

        // 获取所有活动节点 包括开始节点
        // act_hi_actinst
        List<HistoricActivityInstance> hisActivityList = historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .list();
        // 至少要有两布才能返回上一步
        if (CollectionUtils.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
            return error();
        }
        // 获取当前任务节点
        HistoricTaskInstance currentTask = hisTaskList.get(0);
        //  前一个任务
        HistoricTaskInstance lastTask = hisTaskList.get(1);
        //  当前活动
        HistoricActivityInstance currentActivity = hisActivityList
                .stream()
                .filter(e -> currentTask.getId().equals(e.getTaskId())).collect(Collectors.toList())
                .get(0);
        //  前一个活动
        HistoricActivityInstance lastActivity = hisActivityList
                .stream()
                .filter(e -> lastTask.getId().equals(e.getTaskId())).collect(Collectors.toList())
                .get(0);
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());

        //  获取前一个活动节点
        FlowNode lastFlowNode = (FlowNode) bpmnModel
                .getMainProcess()
                .getFlowElement(lastActivity.getActivityId());
        //  获取当前活动节点
        FlowNode currentFlowNode = (FlowNode) bpmnModel
                .getMainProcess()
                .getFlowElement(currentActivity.getActivityId());

        //  临时保存当前活动的原始方向
        List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
        originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
        //  清理活动方向
        currentFlowNode.getOutgoingFlows().clear();

        //  建立新方向
        SequenceFlow newSequenceFlow = new SequenceFlow();
        newSequenceFlow.setId("newSequenceFlowId");
        newSequenceFlow.setSourceFlowElement(currentFlowNode);
        newSequenceFlow.setTargetFlowElement(lastFlowNode);
        List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
        newSequenceFlowList.add(newSequenceFlow);
        //  当前节点指向新的方向
        currentFlowNode.setOutgoingFlows(newSequenceFlowList);
        //  完成当前任务
        taskService.complete(task.getId());
        //  重新查询当前任务
        Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        if (null != nextTask) {
            taskService.setAssignee(nextTask.getId(), lastTask.getAssignee());
        }
        //  恢复原始方向
        currentFlowNode.setOutgoingFlows(originalSequenceFlowList);

        return success();
    }

    @Override
    public InputStream getProcessDiagram(String processInstanceId) {
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        if (null == historicProcessInstance) {
            return null;
        }
        // 获取流程中已经执行的节点，按照先后顺序排序
        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricActivityInstanceStartTime()
                .asc()
                .list();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
        // highLightedActivities 高亮节点
        // highLightedFlows 高亮线
        List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);
        List<String> highLightedActivitiIds = historicActivityInstances
                .stream()
                .map(HistoricActivityInstance::getActivityId)
                .collect(Collectors.toList());

        Set<String> activityIds = runtimeService.createExecutionQuery()
                .processInstanceId(historicProcessInstance.getId())
                .list()
                .stream()
                .map(org.activiti.engine.runtime.Execution::getActivityId)
                .collect(Collectors.toSet());
        ProcessDiagramGeneratorImpl processDiagramGenerator = new ProcessDiagramGeneratorImpl();
        return processDiagramGenerator.generateDiagram(
                bpmnModel,
                highLightedActivitiIds,
                highLightedFlowIds,
                "宋体",
                "微软雅黑",
                "黑体",
                new Color[]{ProcessDiagramCanvas.COLOR_NORMAL, ProcessDiagramCanvas.COLOR_CURRENT},
                activityIds
        );
    }

    /**
     * 处理tasks
     * @param candidates 带有候选组等信息的对象列表
     * @see ITask
     * @see IProcessInstance
     *
     * 在查询到的 ITask 列表中，
     * candidateGroupIds 可能为 [null , null ,null] -> null
     *              如果为 [1, 2, 3] 不变
     * candidateGroupNames 根据  candidateGroupIds 进行查询
     * candidateUserIds 可能为 [1, 2, 3] 不变
     *              如果为 [null , null ,null] -> null
     * candidateUserNames 根据 candidateUserIds 进行查询
     */
    protected List<?> handlerCandidate(List<? extends Candidate> candidates){
        candidates.forEach(candidate -> {
            // 一个任务要么候选组要么候选人，只要设置完一个就结束
            List<Long> candidateGroupIds = candidate.getCandidateGroupIds();
            if (listIsNull(candidateGroupIds)) {
                candidate.setCandidateGroupIds(null);
                candidate.setCandidateGroupNames(null);
            }else {
                candidate.setCandidateGroupNames(assigneeService.selectRoleNameListByGroupIds(candidateGroupIds));
                return;
            }
            List<Long> candidateUserIds = candidate.getCandidateUserIds();
            if (listIsNull(candidateUserIds)) {
                candidate.setCandidateUserIds(null);
                candidate.setCandidateUserNames(null);
            }else {
                candidate.setCandidateUserNames(assigneeService.selectUserNameListByUserIds(candidateUserIds));
            }
        });
        return candidates;
    }

    /**
     * 判断List是否全为 null
     * 在方法中，认为 有一个 元素为 null，那么全为null
     * @return boolean
     */
    protected boolean listIsNull(List<?> list){
        return list == null || list.isEmpty() || list.get(0) == null;
    }

    /**
     * 获取已经流转的线
     * @param bpmnModel                 流程图模板
     * @param historicActivityInstances 历史实例
     */
    protected List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
        // 高亮流程已发生流转的线id集合
        List<String> highLightedFlowIds = new ArrayList<>();
        // 全部活动节点
        List<FlowNode> historicActivityNodes = new ArrayList<>();
        // 已完成的历史活动节点
        List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();

        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
            historicActivityNodes.add(flowNode);
            if (historicActivityInstance.getEndTime() != null) {
                finishedActivityInstances.add(historicActivityInstance);
            }
        }

        FlowNode currentFlowNode = null;
        FlowNode targetFlowNode = null;
        // 遍历已完成的活动实例，从每个实例的outgoingFlows中找到已执行的
        for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
            // 获得当前活动对应的节点信息及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
            List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();

            /**
             * 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转： 1.当前节点是并行网关或兼容网关，则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转 2.当前节点是以上两种类型之外的，通过outgoingFlows查找到的时间最早的流转节点视为有效流转
             */
            if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
                // 遍历历史活动节点，找到匹配流程目标节点的
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
                    if (historicActivityNodes.contains(targetFlowNode)) {
                        highLightedFlowIds.add(targetFlowNode.getId());
                    }
                }
            } else {
                List<Map<String, Object>> tempMapList = new ArrayList<>();
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                        if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
                            Map<String, Object> map = new HashMap<>();
                            map.put("highLightedFlowId", sequenceFlow.getId());
                            map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
                            tempMapList.add(map);
                        }
                    }
                }

                if (!CollectionUtils.isEmpty(tempMapList)) {
                    // 遍历匹配的集合，取得开始时间最早的一个
                    long earliestStamp = 0L;
                    String highLightedFlowId = null;
                    for (Map<String, Object> map : tempMapList) {
                        long highLightedFlowStartTime = Long.parseLong(map.get("highLightedFlowStartTime").toString());
                        if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
                            highLightedFlowId = map.get("highLightedFlowId").toString();
                            earliestStamp = highLightedFlowStartTime;
                        }
                    }

                    highLightedFlowIds.add(highLightedFlowId);
                }

            }

        }
        return highLightedFlowIds;
    }
}
